package jwt

import (
	"crypto/rsa"
	"encoding/base64"
	"strconv"
	"time"

	"auth/lib/token"

	"github.com/dgrijalva/jwt-go"
)

type Jwt struct {
	opts token.Options
}

type userClaims struct {
	UserID int64   `json:"user_id"`
	Roles  []int64 `json:"roles"`
	jwt.StandardClaims
}

func NewTokenProvider(opt ...token.Option) token.Provider {
	return &Jwt{
		opts: token.NewOptions(opt...),
	}
}

func (j *Jwt) Generate(uc *token.UserClaims, opts ...token.GenerateOption) (*token.Token, error) {
	options := token.NewGenerateOptions(opts...)
	expiry := time.Now().Add(options.Expiry)
	created := time.Now()
	t := jwt.NewWithClaims(jwt.SigningMethodRS512, userClaims{
		uc.UserID,
		uc.Roles,
		jwt.StandardClaims{
			Subject:   strconv.FormatInt(uc.UserID, 10),
			IssuedAt:  created.Unix(),
			ExpiresAt: expiry.Unix(),
		},
	})

	pk, err := base64.StdEncoding.DecodeString(j.opts.PrivateKey)
	if err != nil {
		return nil, err
	}
	key, err := jwt.ParseRSAPrivateKeyFromPEM(pk)
	if err != nil {
		return nil, token.ErrEncodingToken
	}

	tk, err := t.SignedString(key)
	if err != nil {
		return nil, err
	}

	return &token.Token{
		Token:   tk,
		Expiry:  expiry,
		Created: created,
	}, nil
}

func (j *Jwt) Parse(tk string) (*token.UserClaims, error) {
	parse, err := jwt.Parse(tk, func(t *jwt.Token) (interface{}, error) {
		pk, err := base64.StdEncoding.DecodeString(j.opts.PublicKey)
		if err != nil {
			return nil, err
		}
		return jwt.ParseRSAPublicKeyFromPEM(pk)
	})
	if err != nil {
		return nil, err
	}

	if claims, ok := parse.Claims.(jwt.MapClaims); ok && parse.Valid {
		c := &token.UserClaims{}
		roles := make([]int64, 0)
		c.Roles = roles
		if claims["user_id"] != "" {
			c.UserID = int64(claims["user_id"].(float64))
		}
		if claims["roles"] != nil {
			s := claims["roles"].([]interface{})
			for _, role := range s {
				f := role.(float64)
				roles = append(roles, int64(f))
			}
			c.Roles = roles
		}
		return c, nil
	}
	return nil, token.ErrInvalidToken
}

func (j *Jwt) Refresh(tk string) (*token.Token, error) {
	t, err := jwt.Parse(tk, func(t *jwt.Token) (interface{}, error) {
		pk, err := base64.StdEncoding.DecodeString(j.opts.PublicKey)
		if err != nil {
			return nil, err
		}
		return jwt.ParseRSAPublicKeyFromPEM(pk)
	})
	if err != nil {
		return nil, err
	}
	claims, ok := t.Claims.(*userClaims)
	if !ok {
		return nil, token.ErrInvalidToken
	}

	expire := time.Now().Add(time.Hour * 72)
	claims.Subject = "1"
	claims.ExpiresAt = expire.Unix()
	pk, err := base64.StdEncoding.DecodeString(j.opts.PrivateKey)
	if err != nil {
		return nil, err
	}
	key, err := jwt.ParseRSAPrivateKeyFromPEM(pk)
	if err != nil {
		return nil, err
	}
	newTk, err := t.SignedString(key)
	if err != nil {
		return nil, err
	}
	return &token.Token{
		Token:   tk,
		Refresh: newTk,
		Created: time.Now(),
		Expiry:  expire,
	}, nil
}

func (j *Jwt) String() string {
	return "jwt"
}

func (j *Jwt) ParseRSAPrivateKeyFromPEM() (*rsa.PrivateKey, error) {
	pk, err := base64.StdEncoding.DecodeString(j.opts.PrivateKey)
	if err != nil {
		return nil, err
	}
	return jwt.ParseRSAPrivateKeyFromPEM(pk)
}
